﻿using DBUtil.Generators;
using DotNetCommon;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Test.SqlServer
{
    /// <summary>
    /// 测试Id和流水号生成,基于进程内缓存
    /// </summary>
    public sealed partial class GeneratorTests : TestBase
    {
        #region 测试流水号
        [TestCategory("测试SNO生成-基于进程缓存")]
        [TestMethod]
        public void NewSNO_ProcessCacheTest()
        {
            using (db.OpenLongConnectOnce())
            {
                //准备测试表
                if (db.JudgeTableExist("test"))
                {
                    db.Manage.DropTable("test");
                }
                db.ExecuteSql(@"create table test(
id int primary key,
sno varchar(50),
name varchar(50)
)");
                db.GeneratorProcessCache.ResetSNO("test", "test", "sno");
                //单个生成
                var sno = db.GeneratorProcessCache.NewSNO("test", "test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000001");
                sno = db.GeneratorProcessCache.NewSNO("test", "test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000002");

                //根据表中已有的sno,继续生成
                var dic = new Dictionary<string, object>();
                var id = db.NewId("test", "id");
                dic.Add("id", id);
                dic.Add("name", "xiaoming" + id);
                dic.Add("sno", sno);
                db.Insert("test", dic);
                db.GeneratorProcessCache.ResetSNO("test", "test", "sno");

                sno = db.GeneratorProcessCache.NewSNO("test", "test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000003");
            };
        }

        [TestCategory("测试SNO生成-基于进程缓存-并发测试")]
        [TestMethod]
        public void NewSNOConcurrency_ProcessCacheTest()
        {
            //准备测试表
            if (db.JudgeTableExist("test"))
            {
                db.Manage.DropTable("test");
            }
            db.ExecuteSql(@"create table test(
id int primary key,
sno varchar(50),
name varchar(50)
)");
            db.GeneratorProcessCache.ResetSNO("test", "test", "sno");
            db.Manage.TruncateTable("dbo","test");
            var tasks = new List<Task>();
            var snores = new System.Collections.Concurrent.ConcurrentQueue<(string sno, int threadid)>();
            for (int i = 0; i < 100; i++)
            {
                tasks.Add(Task.Factory.StartNew(() =>
                {
                    try
                    {
                        var db2 = db.CreateNewDB();
                        using (db2.OpenLongConnectOnce())
                        {
                            for (int j = 0; j < 100; j++)
                            {
                                var sno = db2.GeneratorProcessCache.NewSNO("test", "test", "sno", SerialFormat.CreateFast("SNO"));
                                snores.Enqueue((sno, Thread.CurrentThread.ManagedThreadId));
                            }
                        };
                    }
                    catch (Exception ex)
                    {
                        Debug.Write(ex?.Message + ex?.StackTrace);
                        Debug.Fail(ex?.Message, ex?.StackTrace);
                    }
                }, TaskCreationOptions.LongRunning));
            }
            Task.WaitAll(tasks.ToArray());
            var list = snores.ToList();
            list = list.OrderByDescending(t => t.sno).ToList();
            var g = list.GroupBy(i => i.sno).Where(i => i.Count() > 1).ToList();
            Assert.IsTrue(g.Count == 0);
            var expactCount = 100 * 100;
            Assert.IsTrue(list.FirstOrDefault().sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}{expactCount.ToString().PadLeft(6, '0')}");
            Assert.IsTrue(list.Count == expactCount);
        }
        #endregion

        #region 测试Id生成
        [TestCategory("Id生成-基于进程内缓存")]
        [TestMethod]
        public void NewId_ProcessCacheTest()
        {
            using (db.OpenLongConnectOnce())
            {
                //准备测试表
                if (db.JudgeTableExist("test"))
                {
                    db.Manage.DropTable("test");
                }
                db.ExecuteSql(@"create table test(
id int primary key,
name varchar(50)
)");
                db.GeneratorProcessCache.ResetId("test", "test", "id");
                //单个id生成
                long id = db.GeneratorProcessCache.NewId("test", "test", "id");
                Assert.IsTrue(id == 1);
                id = db.GeneratorProcessCache.NewId("test", "test", "id");
                Assert.IsTrue(id == 2);
                //根据表中已有的id,继续生成
                var dic = new Dictionary<string, object>();
                dic.Add("id", id);
                dic.Add("name", "xiaoming" + id);
                db.Insert("test", dic);
                db.GeneratorProcessCache.ResetId("test", "test", "id");
                id = db.GeneratorProcessCache.NewId("test", "test", "id");
                Assert.IsTrue(id == 3);
            };
        }

        [TestCategory("Id生成-基于进程内缓存-并发测试")]
        [TestMethod]
        public void NewIdConcurrency_ProcessCacheTest()
        {
            //准备测试表
            if (db.JudgeTableExist("test"))
            {
                db.Manage.DropTable("test");
            }
            db.ExecuteSql(@"create table test(
id int primary key,
name varchar(50)
)");
            db.GeneratorProcessCache.ResetId("test", "test", "id");
            db.Manage.TruncateTable("dbo", "test");
            var tasks = new List<Task>();
            var idres = new System.Collections.Concurrent.ConcurrentQueue<(long id, int threadid)>();
            for (int i = 0; i < 100; i++)
            {
                tasks.Add(Task.Factory.StartNew(() =>
                {
                    try
                    {
                        var db2 = db.CreateNewDB();
                        using (db2.OpenLongConnectOnce())
                        {
                            for (int j = 0; j < 100; j++)
                            {
                                var idg = db2.GeneratorProcessCache.NewId("test", "test", "id");
                                idres.Enqueue((idg, Thread.CurrentThread.ManagedThreadId));
                            }
                        };
                    }
                    catch (Exception ex)
                    {
                        Debug.Write(ex?.Message + ex?.StackTrace);
                        Debug.Fail(ex?.Message, ex?.StackTrace);
                    }
                }, TaskCreationOptions.LongRunning));
            }
            Task.WaitAll(tasks.ToArray());
            var list = idres.ToList();
            list = list.OrderByDescending(t => t.id).ToList();
            var expactCount = 100 * 100;
            var g = list.GroupBy(i => i.id).Where(i => i.Count() > 1).ToList();
            Assert.IsTrue(g.Count == 0);
            Assert.IsTrue(list.FirstOrDefault().id == expactCount);
            Assert.IsTrue(list.Count == expactCount);
        }

        #endregion
    }
}
